#
#  Invokers are responsible for 
#
#      1. grabbing work off a job broker (such as a starling or rabbitmq server).
#      2. routing (mapping) that work onto the correct worker method. 
#      3. invoking the worker method, passing any arguments that came off the broker.
#
#   Invokers should implement their own concurrency strategies. For example, 
#   The there is a ThreadedPoller which starts a thread for each Worker class.
#
#   This base Invoker class defines the methods an Invoker needs to implement. 
#  
module Workling
  module Invokers
    class Base

      attr_accessor :sleep_time, :reset_time

      #
      #  call up with super in the subclass constructor.
      #
      def initialize(routing, client_class)
        @routing = routing
        @client_class = client_class
        @sleep_time = Workling.config[:sleep_time] || 2
        @reset_time = Workling.config[:reset_time] || 30
        @@mutex ||= Mutex.new
      end
      
      #
      #  Starts main Invoker Loop. The invoker runs until stop() is called. 
      #
      def listen
        raise NotImplementedError.new("Implement listen() in your Invoker. ")
      end        
      
      #
      #  Gracefully stops the Invoker. The currently executing Jobs should be allowed
      #  to finish. 
      #
      def stop
        raise NotImplementedError.new("Implement stop() in your Invoker. ")
      end
      
      # 
      #  Runs the worker method, given
      #
      #      type: the worker route
      #      args: the arguments to be passed into the worker method.
      #
      def run(type, args)
        worker = @routing[type]
        method = @routing.method_name(type)
        worker.dispatch_to_worker_method(method, args)
      end
      
      # returns the Workling::Base.logger
      def logger; Workling::Base.logger; end
      
      protected
      
        # handle opening and closing of client. pass code block to this method. 
        def connect
          @client = @client_class.new
          @client.connect
      
          begin
            yield
          ensure
            @client.close
            ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord::Base)
          end
        end
      
        #
        #  Loops through the available routes, yielding for each route. 
        #  This continues until @shutdown is set on this instance. 
        #
        def loop_routes
          while(!@shutdown) do
            ensure_activerecord_connection
      
            routes.each do |route|
              break if @shutdown
              yield route
            end
      
            sleep self.sleep_time
          end
        end
      
        #
        #  Returns the complete set of active routes
        #
        def routes
          Workling::Discovery.discovered_workers.map { |clazz| @routing.queue_names_routing_class(clazz) }.flatten
        end
        
        # Thanks for this Brent! 
        #
        #     ...Just a heads up, due to how rails’ MySQL adapter handles this  
        #     call ‘ActiveRecord::Base.connection.active?’, you’ll need 
        #     to wrap the code that checks for a connection in in a mutex.
        #
        #     ....I noticed this while working with a multi-core machine that 
        #     was spawning multiple workling threads. Some of my workling 
        #     threads would hit serious issues at this block of code without 
        #     the mutex.            
        #
        def ensure_activerecord_connection
          if defined?(ActiveRecord::Base)
            @@mutex.synchronize do
              unless ActiveRecord::Base.connection.active?  # Keep MySQL connection alive
                unless ActiveRecord::Base.connection.reconnect!
                  logger.fatal("Failed - Database not available!")
                  break
                end
              end
            end
          end
        end
    end
  end
end
